using System;
using NodaTime.Text;

namespace NodaTime.Serialization.ServiceStackText
{
    public static class NodaSerializerDefinitions
    {
        public static IServiceStackSerializer<Interval> ComplexIntervalSerializer = new ComplexJsonIntervalSerializer(CreateInstantSerializer());

        public static IServiceStackSerializer<Interval> ExtendedIsoIntervalSerializer = new ExtendedIsoIntervalSerializer(CreateInstantSerializer());

        public static IServiceStackSerializer<LocalTime> LocalTimeSerializer =
            new StandardServiceStackSerializer<LocalTime>(
                LocalTimePattern.ExtendedIsoPattern,
                ServiceStackFallbackDeserializers.ToLocalTime);

        public static readonly IServiceStackSerializer<LocalDate> LocalDateSerializer =
            new StandardServiceStackSerializer<LocalDate>(
                LocalDatePattern.IsoPattern,
                ServiceStackFallbackDeserializers.ToLocalDate,
                CreateIsoValidator<LocalDate>(x => x.Calendar));

        public static readonly IServiceStackSerializer<LocalDateTime> LocalDateTimeSerializer =
            new StandardServiceStackSerializer<LocalDateTime>(
                LocalDateTimePattern.ExtendedIsoPattern,
                ServiceStackFallbackDeserializers.ToLocalDateTime,
                CreateIsoValidator<LocalDateTime>(x => x.Calendar));

        public static readonly IServiceStackSerializer<OffsetDateTime> OffsetDateTimeSerializer =
            new StandardServiceStackSerializer<OffsetDateTime>(
                OffsetDateTimePattern.ExtendedIsoPattern,
                ServiceStackFallbackDeserializers.ToOffsetDateTime,
                CreateIsoValidator<OffsetDateTime>(x => x.Calendar));

        public static readonly IServiceStackSerializer<Instant> InstantSerializer = CreateInstantSerializer();

        public static IServiceStackSerializer<ZonedDateTime> CreateZonedDateTimeSerializer(IDateTimeZoneProvider provider)
        {
            return
                new StandardServiceStackSerializer<ZonedDateTime>(
                    ZonedDateTimePattern.CreateWithInvariantCulture(
                        "yyyy'-'MM'-'dd'T'HH':'mm':'ss;FFFFFFFo<G> z",
                        provider),
                    ServiceStackFallbackDeserializers.ToZonedDateTime,
                    CreateIsoValidator<ZonedDateTime>(x => x.Calendar));
        }

        public static IServiceStackSerializer<Period> RoundtripPeriodSerializer =
            new StandardServiceStackSerializer<Period>(PeriodPattern.RoundtripPattern);

        public static IServiceStackSerializer<Period> NormalizingIsoPeriodSerializer =
            new StandardServiceStackSerializer<Period>(PeriodPattern.NormalizingIsoPattern);

        public static IServiceStackSerializer<Duration> DurationSerializer =
            new StandardServiceStackSerializer<Duration>(DurationPattern.CreateWithInvariantCulture("-H:mm:ss.FFFFFFF"));

        public static IServiceStackSerializer<DateTimeZone> CreateDateTimeZoneSerializer(IDateTimeZoneProvider provider)
        {
            return new DateTimeZoneSerializer(provider);
        }

        public static IServiceStackSerializer<Offset> OffsetSerializer =
            new StandardServiceStackSerializer<Offset>(OffsetPattern.GeneralInvariantPattern);
        
        private static IServiceStackSerializer<Instant> CreateInstantSerializer()
        {
            return new StandardServiceStackSerializer<Instant>(
                InstantPattern.ExtendedIsoPattern,
                ServiceStackFallbackDeserializers.ToInstant);
        }

        private static Action<T> CreateIsoValidator<T>(Func<T, CalendarSystem> calendarProjection)
        {
            return value =>
            {
                var calendar = calendarProjection(value);
                // We rely on CalendarSystem.Iso being a singleton here.
                if (calendar != CalendarSystem.Iso)
                {
                    throw new ArgumentException(
                        string.Format("Values of type {0} must (currently) use the ISO calendar in order to be serialized.",
                            typeof(T).Name));
                }
            };
        }
    }
}